﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using PARCS.Core.Contracts;

namespace PARCS.Core
{
    /// <summary>
    /// Channel between two points
    /// </summary>
    public class Channel
    {
        /// <summary>
        /// Socket used by channel
        /// </summary>
        public Socket Socket { get; private set; }

        /// <summary>
        /// Work state of channel
        /// </summary>
        public bool IsWorking { get; set; }

        public int From { get; private set; }

        public int Index { get; private set; }

        public IRecovery Recovery { get; private set; }

        private readonly BinaryReader _input;
        private readonly BinaryWriter _output;

        public Channel(BinaryReader input, BinaryWriter output)
        {
            _input = input;
            _output = output;
        }

        public Channel(BinaryWriter output, BinaryReader input)
            : this(input, output)
        {
        }

        public Channel(BinaryReader input, BinaryWriter output, bool works)
            : this(input, output)
        {
            IsWorking = works;
        }

        public Channel(BinaryWriter output, BinaryReader input, bool works)
            : this(input, output, works)
        {
        }

        /// <summary>
        /// Write Int32 number to channel
        /// </summary>
        /// <param name="x">Parametr</param>
        public void Write(int x)
        {
            if (!IsWorking) return;

            try
            {
                _output.Write(x);
                _output.Flush();
            }
            catch (Exception e)
            {
                IsWorking = false;
                throw new IOException(e.Message);
            }
            //TODO: IRECOVERY
        }

        /// <summary>
        /// Write double number to channel
        /// </summary>
        /// <param name="x">Parametr</param>
        public void Write(double x)
        {
            if (!IsWorking) return;

            try
            {
                _output.Write(x);
                _output.Flush();
            }
            catch (Exception e)
            {
                IsWorking = false;
                throw new IOException(e.Message);
            }
            //TODO: IRECOVERY
        }

        /// <summary>
        /// Write long number to channel
        /// </summary>
        /// <param name="x">Parametr</param>
        public void Write(long x)
        {
            if (!IsWorking) return;

            try
            {
                _output.Write(x);
                _output.Flush();
            }
            catch (Exception e)
            {
                IsWorking = false;
                throw new IOException(e.Message);
            }
            //TODO: IRECOVERY
        }

        /// <summary>
        /// Write bool to channel
        /// </summary>
        /// <param name="x">Parametr</param>
        public void Write(bool x)
        {
            if (!IsWorking) return;

            try
            {
                _output.Write(x);
                _output.Flush();
            }
            catch (Exception e)
            {
                IsWorking = false;
                throw new IOException(e.Message);
            }
            //TODO: IRECOVERY
        }

        /// <summary>
        /// Write byte to channel
        /// </summary>
        /// <param name="x">Parametr</param>
        public void Write(byte x)
        {
            if (!IsWorking) return;

            try
            {
                _output.Write(x);
                _output.Flush();
            }
            catch (Exception e)
            {
                IsWorking = false;
                throw new IOException(e.Message);
            }
            //TODO: IRECOVERY
        }

        /// <summary>
        /// Write array of bytes to channel
        /// </summary>
        /// <param name="x">Parametr</param>
        public void Write(byte[] x)
        {
            if (!IsWorking) return;

            try
            {
                _output.Write(x);
                _output.Flush();
            }
            catch (Exception e)
            {
                IsWorking = false;
                throw new IOException(e.Message);
            }
            //TODO: IRECOVERY
        }

        /// <summary>
        /// Write string to channel
        /// </summary>
        /// <param name="x">Parametr</param>
        public void Write(string x)
        {
            if (!IsWorking || x == null) return;

            try
            {
                _output.Write(x);
                _output.Flush();
            }
            catch (Exception e)
            {
                IsWorking = false;
                throw new IOException(e.Message);
            }
            //TODO: IRECOVERY
        }

        /// <summary>
        /// Writes object to channel
        /// </summary>
        /// <param name="x">Object</param>
        public void Write(object x)
        {
            if (!IsWorking || x == null) return;

            //var memoryStream = new MemoryStream();
            var formatter = new BinaryFormatter();
            //formatter.Serialize(memoryStream, x);

            try
            {
                formatter.Serialize(_output.BaseStream, x);
                //_output.Write(memoryStream.ToArray());

                _output.Flush();
            }
            catch
            {
                IsWorking = false;
                throw new IOException();
            }

            //TODO:IRECOVERY
        }

        public void SendFile(string fullName)
        {
            if (!IsWorking) return;

            try
            {
                Socket.SendFile(fullName);
            }
            catch
            {
                IsWorking = false;
                throw new IOException();
            }

            //TODO:IRECOVERY
        }

        /// <summary>
        /// Flush output stream
        /// </summary>
        public void Flush()
        {
            if (!IsWorking) return;

            try
            {
                _output.Flush();
            }
            catch (Exception)
            {
                IsWorking = false;
                return;
            }

            //TODO:IRECOVERY
        }

        public int ReadInt32()
        {
            if (!IsWorking) return 0;

            do
            {
                try
                {
                    return _input.ReadInt32();
                }
                catch (EndOfStreamException)
                {
                }
                catch (IOException)
                {
                    IsWorking = false;
                    return 0;
                }
            } while (true);
        }

        public long ReadInt64()
        {
            if (!IsWorking) return 0;

            do
            {
                try
                {
                    return _input.ReadInt64();
                }
                catch (EndOfStreamException)
                {
                }
                catch (IOException)
                {
                    IsWorking = false;
                    return 0;
                }
            } while (true);
        }

        public byte ReadByte()
        {
            if (!IsWorking) return 0;

            do
            {
                try
                {
                    return _input.ReadByte();
                }
                catch (EndOfStreamException)
                {
                }
                catch (IOException)
                {
                    IsWorking = false;
                    return 0;
                }
            } while (true);
        }

        public bool ReadBool()
        {
            if (!IsWorking) return 0;

            do
            {
                try
                {
                    return _input.ReadBoolean();
                }
                catch (EndOfStreamException)
                {
                }
                catch (IOException)
                {
                    IsWorking = false;
                    return false;
                }
            } while (true);
        }

        public double ReadDouble()
        {
            if (!IsWorking) return 0;

            do
            {
                try
                {
                    return _input.ReadDouble();
                }
                catch (EndOfStreamException)
                {
                }
                catch (IOException)
                {
                    IsWorking = false;
                    return 0;
                }
            } while (true);
        }

        public byte[] ReadBytes(int count)
        {
            if (!IsWorking) return null;

            do
            {
                try
                {
                    return _input.ReadBytes(count);
                }
                catch (EndOfStreamException)
                {
                }
                catch (IOException)
                {
                    IsWorking = false;
                    return null;
                }
            } while (true);
        }

        public T ReadObject<T>()
        {
            if (!IsWorking) return default(T);

            do
            {
                try
                {
                    var formatter = new BinaryFormatter();
                    return (T)formatter.Deserialize(_input.BaseStream);
                }
                catch (EndOfStreamException)
                {
                }
                catch (IOException)
                {
                    IsWorking = false;
                    return default(T);
                }
            } while (true);
        }
    }
}
